package org.pupil.rpc.utils;

import lombok.SneakyThrows;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 属性工具类
 */
public class PropertyUtil {
    // springBoot 版本
    private static int springBootVersion = 1;

    static {
        try {
            // 获取处理松散绑定的属性解析器的类
            Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");
        } catch (ClassNotFoundException e) {
            // 如果失败，设置springBoot 版本为2
            springBootVersion = 2;
        }
    }

    /**
     * Spring Boot 1.x is compatible with Spring Boot 2.x by Using Java Reflect.
     * Spring Boot 1.x通过使用Java Reflect与Spring Boot 2.x兼容
     *
     * @param environment : the environment context 环境上下文
     * @param prefix      : the prefix part of property key 属性键的前缀部分
     * @param targetClass : the target class type of result 结果的目标类类型
     * @param <T>         : refer to @param targetClass
     * @return T
     * @SuppressWarnings("unchecked") 用于抑制编译器产生的警告信息，这里压制泛型转换的警告
     */
    @SuppressWarnings("unchecked")
    public static <T> T handle(final Environment environment, final String prefix, final Class<T> targetClass) {
        switch (springBootVersion) {
            case 1:
                return (T) v1(environment, prefix);
            default:
                return (T) v2(environment, prefix, targetClass);
        }
    }

    /**
     * Spring Boot 1.X 处理方法
     *
     * @param environment 环境上下文
     * @param prefix      属性键的前缀部分
     * @return
     * @SneakyThrows 编译器会自动在方法体中加入异常处理逻辑，将受检查异常转换为不受检查异常（即 RuntimeException）抛出
     */
    @SneakyThrows
    private static Object v1(final Environment environment, final String prefix) {
        try {
            // 获取处理松散绑定的属性解析器的类
            Class<?> resolverClass = Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");
            // 获取属性解析器的构造器
            Constructor<?> resolverConstructor = resolverClass.getDeclaredConstructor(PropertyResolver.class);
            // 获取getSubProperties方法，用于获取指定属性前缀下的子属性
            Method getSubPropertiesMethod = resolverClass.getDeclaredMethod("getSubProperties", String.class);
            // 通过构造器, 实例化RelaxedPropertyResolver
            Object resolverObject = resolverConstructor.newInstance(environment);
            // 处理前缀
            String prefixParam = prefix.endsWith(".") ? prefix : prefix + ".";
            // 返回包含子属性键值对 或者 子属性键值对集合 的映射对象
            return getSubPropertiesMethod.invoke(resolverObject, prefixParam);
        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException
                | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            throw new RuntimeException(ex.getMessage(), ex);
        }
    }

    /**
     * Spring Boot 2.X 处理方法
     *
     * springboot2.X 中松紧绑定收缩， 所有前缀必须是kebab格式（小写，连字符分隔）
     * 属性名称可以使用 kebab-case（my-name），camel-case（myName）或 snake-case（my_name）
     * @param environment
     * @param prefix
     * @param targetClass
     * @return
     * @SneakyThrows 编译器会自动在方法体中加入异常处理逻辑，将受检查异常转换为不受检查异常（即 RuntimeException）抛出
     */
    @SneakyThrows
    private static Object v2(final Environment environment, final String prefix, final Class<?> targetClass) {
        try {
            // 获取属性的绑定操作类的类
            Class<?> binderClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");
            // 获取getMethod方法，参数：Environment，用于获取绑定器对象
            Method getMethod = binderClass.getDeclaredMethod("get", Environment.class);
            // 获取bindMethod方法，参数：binder，属性的前缀，目标Java类型，用于将配置文件中以指定前缀开头的属性绑定到属性声明时类型对象上，并返回该实例
            Method bindMethod = binderClass.getDeclaredMethod("bind", String.class, Class.class);
            // 实例化binder
            Object binderObject = getMethod.invoke(null, environment);
            // 获取没有以.结尾的前缀
            String prefixParam = prefix.endsWith(".") ? prefix.substring(0, prefix.length() - 1) : prefix;
            // 属性绑定到目标属性声明时类型对象上的实例对象
            Object bindResultObject = bindMethod.invoke(binderObject, prefixParam, targetClass);
            // 获取一个名为 get 的方法，用于获取绑定到目标属性声明时类型对象上的实例对象的内容
            Method resultGetMethod = bindResultObject.getClass().getDeclaredMethod("get");
            // 返回属性
            return resultGetMethod.invoke(bindResultObject);
        } catch (final ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException ex) {
            return null;
        }
    }
}
